tor-browser

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

exr.cc (8039B)


      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/extras/dec/exr.h"
      7 
      8 #include <cstdint>
      9 
     10 #include "lib/extras/dec/color_hints.h"
     11 #include "lib/extras/packed_image.h"
     12 #include "lib/jxl/base/common.h"
     13 #include "lib/jxl/base/span.h"
     14 #include "lib/jxl/base/status.h"
     15 
     16 #if !JPEGXL_ENABLE_EXR
     17 
     18 namespace jxl {
     19 namespace extras {
     20 bool CanDecodeEXR() { return false; }
     21 
     22 Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
     23                      PackedPixelFile* ppf,
     24                      const SizeConstraints* constraints) {
     25  (void)bytes;
     26  (void)color_hints;
     27  (void)ppf;
     28  (void)constraints;
     29  return JXL_FAILURE("EXR is not supported");
     30 }
     31 }  // namespace extras
     32 }  // namespace jxl
     33 
     34 #else  // JPEGXL_ENABLE_EXR
     35 
     36 #include <ImfChromaticitiesAttribute.h>
     37 #include <ImfIO.h>
     38 #include <ImfRgbaFile.h>
     39 #include <ImfStandardAttributes.h>
     40 
     41 #include <vector>
     42 
     43 #ifdef __EXCEPTIONS
     44 #include <stdexcept>
     45 #define JXL_EXR_THROW_LENGTH_ERROR() throw std::length_error("");
     46 #else  // __EXCEPTIONS
     47 #define JXL_EXR_THROW_LENGTH_ERROR() JXL_CRASH()
     48 #endif  // __EXCEPTIONS
     49 
     50 namespace jxl {
     51 namespace extras {
     52 
     53 namespace {
     54 
     55 namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
     56 
     57 // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
     58 // uint64_t as recommended causes build failures with previous OpenEXR versions
     59 // on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
     60 // to uint64_t. This alternative should work in all cases.
     61 using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
     62 
     63 constexpr int kExrBitsPerSample = 16;
     64 constexpr int kExrAlphaBits = 16;
     65 
     66 class InMemoryIStream : public OpenEXR::IStream {
     67 public:
     68  // The data pointed to by `bytes` must outlive the InMemoryIStream.
     69  explicit InMemoryIStream(const Span<const uint8_t> bytes)
     70      : IStream(/*fileName=*/""), bytes_(bytes) {}
     71 
     72  bool isMemoryMapped() const override { return true; }
     73  char* readMemoryMapped(const int n) override {
     74    if (pos_ + n < pos_ || pos_ + n > bytes_.size()) {
     75      JXL_EXR_THROW_LENGTH_ERROR();
     76    }
     77    char* const result =
     78        const_cast<char*>(reinterpret_cast<const char*>(bytes_.data() + pos_));
     79    pos_ += n;
     80    return result;
     81  }
     82  bool read(char c[], const int n) override {
     83    std::copy_n(readMemoryMapped(n), n, c);
     84    return pos_ < bytes_.size();
     85  }
     86 
     87  ExrInt64 tellg() override { return pos_; }
     88  void seekg(const ExrInt64 pos) override {
     89    if (pos >= bytes_.size()) {
     90      JXL_EXR_THROW_LENGTH_ERROR();
     91    }
     92    pos_ = pos;
     93  }
     94 
     95 private:
     96  const Span<const uint8_t> bytes_;
     97  size_t pos_ = 0;
     98 };
     99 
    100 }  // namespace
    101 
    102 bool CanDecodeEXR() { return true; }
    103 
    104 Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
    105                      PackedPixelFile* ppf,
    106                      const SizeConstraints* constraints) {
    107  InMemoryIStream is(bytes);
    108 
    109 #ifdef __EXCEPTIONS
    110  std::unique_ptr<OpenEXR::RgbaInputFile> input_ptr;
    111  try {
    112    input_ptr = jxl::make_unique<OpenEXR::RgbaInputFile>(is);
    113  } catch (...) {
    114    // silently return false if it is not an EXR file
    115    return false;
    116  }
    117  OpenEXR::RgbaInputFile& input = *input_ptr;
    118 #else
    119  OpenEXR::RgbaInputFile input(is);
    120 #endif
    121 
    122  if ((input.channels() & OpenEXR::RgbaChannels::WRITE_RGB) !=
    123      OpenEXR::RgbaChannels::WRITE_RGB) {
    124    return JXL_FAILURE("only RGB OpenEXR files are supported");
    125  }
    126  const bool has_alpha = (input.channels() & OpenEXR::RgbaChannels::WRITE_A) ==
    127                         OpenEXR::RgbaChannels::WRITE_A;
    128 
    129  const float intensity_target = OpenEXR::hasWhiteLuminance(input.header())
    130                                     ? OpenEXR::whiteLuminance(input.header())
    131                                     : 0;
    132 
    133  auto image_size = input.displayWindow().size();
    134  // Size is computed as max - min, but both bounds are inclusive.
    135  ++image_size.x;
    136  ++image_size.y;
    137 
    138  ppf->info.xsize = image_size.x;
    139  ppf->info.ysize = image_size.y;
    140  ppf->info.num_color_channels = 3;
    141 
    142  const JxlDataType data_type =
    143      kExrBitsPerSample == 16 ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT;
    144  const JxlPixelFormat format{
    145      /*num_channels=*/3u + (has_alpha ? 1u : 0u),
    146      /*data_type=*/data_type,
    147      /*endianness=*/JXL_NATIVE_ENDIAN,
    148      /*align=*/0,
    149  };
    150  ppf->frames.clear();
    151  // Allocates the frame buffer.
    152  {
    153    JXL_ASSIGN_OR_RETURN(
    154        PackedFrame frame,
    155        PackedFrame::Create(image_size.x, image_size.y, format));
    156    ppf->frames.emplace_back(std::move(frame));
    157  }
    158  const auto& frame = ppf->frames.back();
    159 
    160  const int row_size = input.dataWindow().size().x + 1;
    161  // Number of rows to read at a time.
    162  // https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf
    163  // recommends reading the whole file at once.
    164  const int y_chunk_size = input.displayWindow().size().y + 1;
    165  std::vector<OpenEXR::Rgba> input_rows(row_size * y_chunk_size);
    166  for (int start_y =
    167           std::max(input.dataWindow().min.y, input.displayWindow().min.y);
    168       start_y <=
    169       std::min(input.dataWindow().max.y, input.displayWindow().max.y);
    170       start_y += y_chunk_size) {
    171    // Inclusive.
    172    const int end_y = std::min(
    173        start_y + y_chunk_size - 1,
    174        std::min(input.dataWindow().max.y, input.displayWindow().max.y));
    175    input.setFrameBuffer(
    176        input_rows.data() - input.dataWindow().min.x - start_y * row_size,
    177        /*xStride=*/1, /*yStride=*/row_size);
    178    input.readPixels(start_y, end_y);
    179    for (int exr_y = start_y; exr_y <= end_y; ++exr_y) {
    180      const int image_y = exr_y - input.displayWindow().min.y;
    181      const OpenEXR::Rgba* const JXL_RESTRICT input_row =
    182          &input_rows[(exr_y - start_y) * row_size];
    183      uint8_t* row = static_cast<uint8_t*>(frame.color.pixels()) +
    184                     frame.color.stride * image_y;
    185      const uint32_t pixel_size =
    186          (3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8;
    187      for (int exr_x =
    188               std::max(input.dataWindow().min.x, input.displayWindow().min.x);
    189           exr_x <=
    190           std::min(input.dataWindow().max.x, input.displayWindow().max.x);
    191           ++exr_x) {
    192        const int image_x = exr_x - input.displayWindow().min.x;
    193        // TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable
    194        memcpy(row + image_x * pixel_size,
    195               input_row + (exr_x - input.dataWindow().min.x), pixel_size);
    196      }
    197    }
    198  }
    199 
    200  ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
    201  ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
    202  ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
    203  ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
    204  if (OpenEXR::hasChromaticities(input.header())) {
    205    ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM;
    206    ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM;
    207    const auto& chromaticities = OpenEXR::chromaticities(input.header());
    208    ppf->color_encoding.primaries_red_xy[0] = chromaticities.red.x;
    209    ppf->color_encoding.primaries_red_xy[1] = chromaticities.red.y;
    210    ppf->color_encoding.primaries_green_xy[0] = chromaticities.green.x;
    211    ppf->color_encoding.primaries_green_xy[1] = chromaticities.green.y;
    212    ppf->color_encoding.primaries_blue_xy[0] = chromaticities.blue.x;
    213    ppf->color_encoding.primaries_blue_xy[1] = chromaticities.blue.y;
    214    ppf->color_encoding.white_point_xy[0] = chromaticities.white.x;
    215    ppf->color_encoding.white_point_xy[1] = chromaticities.white.y;
    216  }
    217 
    218  // EXR uses binary16 or binary32 floating point format.
    219  ppf->info.bits_per_sample = kExrBitsPerSample;
    220  ppf->info.exponent_bits_per_sample = kExrBitsPerSample == 16 ? 5 : 8;
    221  if (has_alpha) {
    222    ppf->info.alpha_bits = kExrAlphaBits;
    223    ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample;
    224    ppf->info.alpha_premultiplied = JXL_TRUE;
    225  }
    226  ppf->info.intensity_target = intensity_target;
    227  return true;
    228 }
    229 
    230 }  // namespace extras
    231 }  // namespace jxl
    232 
    233 #endif  // JPEGXL_ENABLE_EXR