enc_jpeg_data.cc (14477B)
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/jpeg/enc_jpeg_data.h" 7 8 #include <brotli/encode.h> 9 #include <jxl/cms.h> 10 #include <jxl/memory_manager.h> 11 #include <jxl/types.h> 12 13 #include <cstdint> 14 15 #include "lib/jxl/base/sanitizers.h" 16 #include "lib/jxl/base/status.h" 17 #include "lib/jxl/codec_in_out.h" 18 #include "lib/jxl/enc_aux_out.h" 19 #include "lib/jxl/enc_bit_writer.h" 20 #include "lib/jxl/image_bundle.h" 21 #include "lib/jxl/jpeg/enc_jpeg_data_reader.h" 22 #include "lib/jxl/luminance.h" 23 24 namespace jxl { 25 namespace jpeg { 26 27 namespace { 28 29 constexpr int BITS_IN_JSAMPLE = 8; 30 using ByteSpan = Span<const uint8_t>; 31 32 // TODO(eustas): move to jpeg_data, to use from codec_jpg as well. 33 // See if there is a canonically chunked ICC profile and mark corresponding 34 // app-tags with AppMarkerType::kICC. 35 Status DetectIccProfile(JPEGData& jpeg_data) { 36 JXL_ENSURE(jpeg_data.app_data.size() == jpeg_data.app_marker_type.size()); 37 size_t num_icc = 0; 38 size_t num_icc_jpeg = 0; 39 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { 40 const auto& app = jpeg_data.app_data[i]; 41 size_t pos = 0; 42 if (app[pos++] != 0xE2) continue; 43 // At least APPn + size; otherwise it should be intermarker-data. 44 JXL_ENSURE(app.size() >= 3); 45 size_t tag_length = (app[pos] << 8) + app[pos + 1]; 46 pos += 2; 47 JXL_ENSURE(app.size() == tag_length + 1); 48 // Empty payload is 2 bytes for tag length itself + signature 49 if (tag_length < 2 + sizeof kIccProfileTag) continue; 50 51 if (memcmp(&app[pos], kIccProfileTag, sizeof kIccProfileTag) != 0) continue; 52 pos += sizeof kIccProfileTag; 53 uint8_t chunk_id = app[pos++]; 54 uint8_t num_chunks = app[pos++]; 55 if (chunk_id != num_icc + 1) continue; 56 if (num_icc_jpeg == 0) num_icc_jpeg = num_chunks; 57 if (num_icc_jpeg != num_chunks) continue; 58 num_icc++; 59 jpeg_data.app_marker_type[i] = AppMarkerType::kICC; 60 } 61 if (num_icc != num_icc_jpeg) { 62 return JXL_FAILURE("Invalid ICC chunks"); 63 } 64 return true; 65 } 66 67 bool GetMarkerPayload(const uint8_t* data, size_t size, ByteSpan* payload) { 68 if (size < 3) { 69 return false; 70 } 71 size_t hi = data[1]; 72 size_t lo = data[2]; 73 size_t internal_size = (hi << 8u) | lo; 74 // Second byte of marker is not counted towards size. 75 if (internal_size != size - 1) { 76 return false; 77 } 78 // cut second marker byte and "length" from payload. 79 *payload = ByteSpan(data, size); 80 if (!payload->remove_prefix(3)) return false; 81 return true; 82 } 83 84 Status DetectBlobs(jpeg::JPEGData& jpeg_data) { 85 JXL_ENSURE(jpeg_data.app_data.size() == jpeg_data.app_marker_type.size()); 86 bool have_exif = false; 87 bool have_xmp = false; 88 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { 89 auto& marker = jpeg_data.app_data[i]; 90 if (marker.empty() || marker[0] != kApp1) { 91 continue; 92 } 93 ByteSpan payload; 94 if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) { 95 // Something is wrong with this marker; does not care. 96 continue; 97 } 98 if (!have_exif && payload.size() > sizeof kExifTag && 99 !memcmp(payload.data(), kExifTag, sizeof kExifTag)) { 100 jpeg_data.app_marker_type[i] = AppMarkerType::kExif; 101 have_exif = true; 102 } 103 if (!have_xmp && payload.size() >= sizeof kXMPTag && 104 !memcmp(payload.data(), kXMPTag, sizeof kXMPTag)) { 105 jpeg_data.app_marker_type[i] = AppMarkerType::kXMP; 106 have_xmp = true; 107 } 108 } 109 return true; 110 } 111 112 Status ParseChunkedMarker(const jpeg::JPEGData& src, uint8_t marker_type, 113 const ByteSpan& tag, IccBytes* output, 114 bool allow_permutations = false) { 115 output->clear(); 116 117 std::vector<ByteSpan> chunks; 118 std::vector<bool> presence; 119 size_t expected_number_of_parts = 0; 120 bool is_first_chunk = true; 121 size_t ordinal = 0; 122 for (const auto& marker : src.app_data) { 123 if (marker.empty() || marker[0] != marker_type) { 124 continue; 125 } 126 ByteSpan payload; 127 if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) { 128 // Something is wrong with this marker; does not care. 129 continue; 130 } 131 if ((payload.size() < tag.size()) || 132 memcmp(payload.data(), tag.data(), tag.size()) != 0) { 133 continue; 134 } 135 JXL_RETURN_IF_ERROR(payload.remove_prefix(tag.size())); 136 if (payload.size() < 2) { 137 return JXL_FAILURE("Chunk is too small."); 138 } 139 uint8_t index = payload[0]; 140 uint8_t total = payload[1]; 141 ordinal++; 142 if (!allow_permutations) { 143 if (index != ordinal) return JXL_FAILURE("Invalid chunk order."); 144 } 145 146 JXL_RETURN_IF_ERROR(payload.remove_prefix(2)); 147 148 JXL_RETURN_IF_ERROR(total != 0); 149 if (is_first_chunk) { 150 is_first_chunk = false; 151 expected_number_of_parts = total; 152 // 1-based indices; 0-th element is added for convenience. 153 chunks.resize(total + 1); 154 presence.resize(total + 1); 155 } else { 156 JXL_RETURN_IF_ERROR(expected_number_of_parts == total); 157 } 158 159 if (index == 0 || index > total) { 160 return JXL_FAILURE("Invalid chunk index."); 161 } 162 163 if (presence[index]) { 164 return JXL_FAILURE("Duplicate chunk."); 165 } 166 presence[index] = true; 167 chunks[index] = payload; 168 } 169 170 for (size_t i = 0; i < expected_number_of_parts; ++i) { 171 // 0-th element is not used. 172 size_t index = i + 1; 173 if (!presence[index]) { 174 return JXL_FAILURE("Missing chunk."); 175 } 176 chunks[index].AppendTo(*output); 177 } 178 179 return true; 180 } 181 182 Status SetBlobsFromJpegData(const jpeg::JPEGData& jpeg_data, Blobs* blobs) { 183 for (const auto& marker : jpeg_data.app_data) { 184 if (marker.empty() || marker[0] != kApp1) { 185 continue; 186 } 187 ByteSpan payload; 188 if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) { 189 // Something is wrong with this marker; does not care. 190 continue; 191 } 192 if (payload.size() >= sizeof kExifTag && 193 !memcmp(payload.data(), kExifTag, sizeof kExifTag)) { 194 if (blobs->exif.empty()) { 195 blobs->exif.resize(payload.size() - sizeof kExifTag); 196 memcpy(blobs->exif.data(), payload.data() + sizeof kExifTag, 197 payload.size() - sizeof kExifTag); 198 } else { 199 JXL_WARNING( 200 "ReJPEG: multiple Exif blobs, storing only first one in the JPEG " 201 "XL container\n"); 202 } 203 } 204 if (payload.size() >= sizeof kXMPTag && 205 !memcmp(payload.data(), kXMPTag, sizeof kXMPTag)) { 206 if (blobs->xmp.empty()) { 207 blobs->xmp.resize(payload.size() - sizeof kXMPTag); 208 memcpy(blobs->xmp.data(), payload.data() + sizeof kXMPTag, 209 payload.size() - sizeof kXMPTag); 210 } else { 211 JXL_WARNING( 212 "ReJPEG: multiple XMP blobs, storing only first one in the JPEG " 213 "XL container\n"); 214 } 215 } 216 } 217 return true; 218 } 219 220 inline bool IsJPG(const Span<const uint8_t> bytes) { 221 return bytes.size() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xD8; 222 } 223 224 } // namespace 225 226 Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg, 227 ColorEncoding* color_encoding) { 228 IccBytes icc_profile; 229 if (!ParseChunkedMarker(jpg, kApp2, ByteSpan(kIccProfileTag), &icc_profile)) { 230 JXL_WARNING("ReJPEG: corrupted ICC profile\n"); 231 icc_profile.clear(); 232 } 233 234 if (icc_profile.empty()) { 235 bool is_gray = (jpg.components.size() == 1); 236 *color_encoding = ColorEncoding::SRGB(is_gray); 237 } else { 238 JXL_RETURN_IF_ERROR( 239 color_encoding->SetICC(std::move(icc_profile), JxlGetDefaultCms())); 240 } 241 return true; 242 } 243 244 Status SetChromaSubsamplingFromJpegData(const JPEGData& jpg, 245 YCbCrChromaSubsampling* cs) { 246 size_t nbcomp = jpg.components.size(); 247 if (nbcomp != 1 && nbcomp != 3) { 248 return JXL_FAILURE("Cannot recompress JPEGs with neither 1 nor 3 channels"); 249 } 250 if (nbcomp == 3) { 251 uint8_t hsample[3]; 252 uint8_t vsample[3]; 253 for (size_t i = 0; i < nbcomp; i++) { 254 hsample[i] = jpg.components[i].h_samp_factor; 255 vsample[i] = jpg.components[i].v_samp_factor; 256 } 257 JXL_RETURN_IF_ERROR(cs->Set(hsample, vsample)); 258 } else if (nbcomp == 1) { 259 uint8_t hsample[3]; 260 uint8_t vsample[3]; 261 for (size_t i = 0; i < 3; i++) { 262 hsample[i] = jpg.components[0].h_samp_factor; 263 vsample[i] = jpg.components[0].v_samp_factor; 264 } 265 JXL_RETURN_IF_ERROR(cs->Set(hsample, vsample)); 266 } 267 return true; 268 } 269 270 Status SetColorTransformFromJpegData(const JPEGData& jpg, 271 ColorTransform* color_transform) { 272 size_t nbcomp = jpg.components.size(); 273 if (nbcomp != 1 && nbcomp != 3) { 274 return JXL_FAILURE("Cannot recompress JPEGs with neither 1 nor 3 channels"); 275 } 276 bool is_rgb = false; 277 { 278 const auto& markers = jpg.marker_order; 279 // If there is a JFIF marker, this is YCbCr. Otherwise... 280 if (std::find(markers.begin(), markers.end(), 0xE0) == markers.end()) { 281 // Try to find an 'Adobe' marker. 282 size_t app_markers = 0; 283 size_t i = 0; 284 for (; i < markers.size(); i++) { 285 // This is an APP marker. 286 if ((markers[i] & 0xF0) == 0xE0) { 287 JXL_ENSURE(app_markers < jpg.app_data.size()); 288 // APP14 marker 289 if (markers[i] == 0xEE) { 290 const auto& data = jpg.app_data[app_markers]; 291 if (data.size() == 15 && data[3] == 'A' && data[4] == 'd' && 292 data[5] == 'o' && data[6] == 'b' && data[7] == 'e') { 293 // 'Adobe' marker. 294 is_rgb = data[14] == 0; 295 break; 296 } 297 } 298 app_markers++; 299 } 300 } 301 302 if (i == markers.size()) { 303 // No 'Adobe' marker, guess from component IDs. 304 is_rgb = nbcomp == 3 && jpg.components[0].id == 'R' && 305 jpg.components[1].id == 'G' && jpg.components[2].id == 'B'; 306 } 307 } 308 } 309 *color_transform = 310 (!is_rgb || nbcomp == 1) ? ColorTransform::kYCbCr : ColorTransform::kNone; 311 return true; 312 } 313 314 Status EncodeJPEGData(JxlMemoryManager* memory_manager, JPEGData& jpeg_data, 315 std::vector<uint8_t>* bytes, 316 const CompressParams& cparams) { 317 bytes->clear(); 318 jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(), 319 AppMarkerType::kUnknown); 320 JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data)); 321 JXL_RETURN_IF_ERROR(DetectBlobs(jpeg_data)); 322 323 size_t total_data = 0; 324 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { 325 if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) { 326 continue; 327 } 328 total_data += jpeg_data.app_data[i].size(); 329 } 330 for (const auto& data : jpeg_data.com_data) { 331 total_data += data.size(); 332 } 333 for (const auto& data : jpeg_data.inter_marker_data) { 334 total_data += data.size(); 335 } 336 total_data += jpeg_data.tail_data.size(); 337 size_t brotli_capacity = BrotliEncoderMaxCompressedSize(total_data); 338 339 BitWriter writer{memory_manager}; 340 JXL_RETURN_IF_ERROR( 341 Bundle::Write(jpeg_data, &writer, LayerType::Header, nullptr)); 342 writer.ZeroPadToByte(); 343 { 344 PaddedBytes serialized_jpeg_data = std::move(writer).TakeBytes(); 345 bytes->reserve(serialized_jpeg_data.size() + brotli_capacity); 346 Bytes(serialized_jpeg_data).AppendTo(*bytes); 347 } 348 349 BrotliEncoderState* brotli_enc = 350 BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); 351 int effort = cparams.brotli_effort; 352 if (effort < 0) effort = 11 - static_cast<int>(cparams.speed_tier); 353 BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort); 354 size_t initial_size = bytes->size(); 355 BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_SIZE_HINT, total_data); 356 bytes->resize(initial_size + brotli_capacity); 357 size_t enc_size = 0; 358 auto br_append = [&](const std::vector<uint8_t>& data, bool last) -> Status { 359 size_t available_in = data.size(); 360 const uint8_t* in = data.data(); 361 uint8_t* out = &(*bytes)[initial_size + enc_size]; 362 do { 363 uint8_t* out_before = out; 364 msan::MemoryIsInitialized(in, available_in); 365 JXL_ENSURE(BrotliEncoderCompressStream( 366 brotli_enc, last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS, 367 &available_in, &in, &brotli_capacity, &out, &enc_size)); 368 msan::UnpoisonMemory(out_before, out - out_before); 369 } while (FROM_JXL_BOOL(BrotliEncoderHasMoreOutput(brotli_enc)) || 370 available_in > 0); 371 return true; 372 }; 373 374 for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { 375 if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) { 376 continue; 377 } 378 JXL_RETURN_IF_ERROR(br_append(jpeg_data.app_data[i], /*last=*/false)); 379 } 380 for (const auto& data : jpeg_data.com_data) { 381 JXL_RETURN_IF_ERROR(br_append(data, /*last=*/false)); 382 } 383 for (const auto& data : jpeg_data.inter_marker_data) { 384 JXL_RETURN_IF_ERROR(br_append(data, /*last=*/false)); 385 } 386 JXL_RETURN_IF_ERROR(br_append(jpeg_data.tail_data, /*last=*/true)); 387 BrotliEncoderDestroyInstance(brotli_enc); 388 bytes->resize(initial_size + enc_size); 389 return true; 390 } 391 392 Status DecodeImageJPG(const Span<const uint8_t> bytes, CodecInOut* io) { 393 if (!IsJPG(bytes)) return false; 394 JxlMemoryManager* memory_manager = io->memory_manager; 395 io->frames.clear(); 396 io->frames.reserve(1); 397 io->frames.emplace_back(memory_manager, &io->metadata.m); 398 io->Main().jpeg_data = make_unique<jpeg::JPEGData>(); 399 jpeg::JPEGData* jpeg_data = io->Main().jpeg_data.get(); 400 if (!jpeg::ReadJpeg(bytes.data(), bytes.size(), jpeg::JpegReadMode::kReadAll, 401 jpeg_data)) { 402 return JXL_FAILURE("Error reading JPEG"); 403 } 404 JXL_RETURN_IF_ERROR( 405 SetColorEncodingFromJpegData(*jpeg_data, &io->metadata.m.color_encoding)); 406 JXL_RETURN_IF_ERROR(SetBlobsFromJpegData(*jpeg_data, &io->blobs)); 407 JXL_RETURN_IF_ERROR(SetChromaSubsamplingFromJpegData( 408 *jpeg_data, &io->Main().chroma_subsampling)); 409 JXL_RETURN_IF_ERROR( 410 SetColorTransformFromJpegData(*jpeg_data, &io->Main().color_transform)); 411 412 io->metadata.m.SetIntensityTarget(kDefaultIntensityTarget); 413 io->metadata.m.SetUintSamples(BITS_IN_JSAMPLE); 414 JXL_ASSIGN_OR_RETURN( 415 Image3F tmp, 416 Image3F::Create(memory_manager, jpeg_data->width, jpeg_data->height)); 417 JXL_RETURN_IF_ERROR( 418 io->SetFromImage(std::move(tmp), io->metadata.m.color_encoding)); 419 SetIntensityTarget(&io->metadata.m); 420 return true; 421 } 422 423 } // namespace jpeg 424 } // namespace jxl