libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

exr.cc (7093B)


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