tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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