libjxl

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

exr.cc (6910B)


      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/enc/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 #include <jxl/codestream_header.h>
     15 
     16 #include <vector>
     17 
     18 #include "lib/extras/packed_image.h"
     19 #include "lib/jxl/base/byte_order.h"
     20 
     21 namespace jxl {
     22 namespace extras {
     23 
     24 #if JPEGXL_ENABLE_EXR
     25 namespace {
     26 
     27 namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
     28 namespace Imath = IMATH_NAMESPACE;
     29 
     30 // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
     31 // uint64_t as recommended causes build failures with previous OpenEXR versions
     32 // on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
     33 // to uint64_t. This alternative should work in all cases.
     34 using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
     35 
     36 class InMemoryOStream : public OpenEXR::OStream {
     37  public:
     38   // `bytes` must outlive the InMemoryOStream.
     39   explicit InMemoryOStream(std::vector<uint8_t>* const bytes)
     40       : OStream(/*fileName=*/""), bytes_(*bytes) {}
     41 
     42   void write(const char c[], const int n) override {
     43     if (bytes_.size() < pos_ + n) {
     44       bytes_.resize(pos_ + n);
     45     }
     46     std::copy_n(c, n, bytes_.begin() + pos_);
     47     pos_ += n;
     48   }
     49 
     50   ExrInt64 tellp() override { return pos_; }
     51   void seekp(const ExrInt64 pos) override {
     52     if (bytes_.size() + 1 < pos) {
     53       bytes_.resize(pos - 1);
     54     }
     55     pos_ = pos;
     56   }
     57 
     58  private:
     59   std::vector<uint8_t>& bytes_;
     60   size_t pos_ = 0;
     61 };
     62 
     63 // Loads a Big-Endian float
     64 float LoadBEFloat(const uint8_t* p) {
     65   uint32_t u = LoadBE32(p);
     66   float result;
     67   memcpy(&result, &u, 4);
     68   return result;
     69 }
     70 
     71 // Loads a Little-Endian float
     72 float LoadLEFloat(const uint8_t* p) {
     73   uint32_t u = LoadLE32(p);
     74   float result;
     75   memcpy(&result, &u, 4);
     76   return result;
     77 }
     78 
     79 Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info,
     80                       const JxlColorEncoding& c_enc, ThreadPool* pool,
     81                       std::vector<uint8_t>* bytes) {
     82   OpenEXR::setGlobalThreadCount(0);
     83 
     84   const size_t xsize = info.xsize;
     85   const size_t ysize = info.ysize;
     86   const bool has_alpha = info.alpha_bits > 0;
     87   const bool alpha_is_premultiplied = FROM_JXL_BOOL(info.alpha_premultiplied);
     88 
     89   if (info.num_color_channels != 3 ||
     90       c_enc.color_space != JXL_COLOR_SPACE_RGB ||
     91       c_enc.transfer_function != JXL_TRANSFER_FUNCTION_LINEAR) {
     92     return JXL_FAILURE("Unsupported color encoding for OpenEXR output.");
     93   }
     94 
     95   const size_t num_channels = 3 + (has_alpha ? 1 : 0);
     96   const JxlPixelFormat format = image.format;
     97 
     98   if (format.data_type != JXL_TYPE_FLOAT) {
     99     return JXL_FAILURE("Unsupported pixel format for OpenEXR output");
    100   }
    101 
    102   const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels());
    103   size_t in_stride = num_channels * 4 * xsize;
    104 
    105   OpenEXR::Header header(xsize, ysize);
    106   OpenEXR::Chromaticities chromaticities;
    107   chromaticities.red =
    108       Imath::V2f(c_enc.primaries_red_xy[0], c_enc.primaries_red_xy[1]);
    109   chromaticities.green =
    110       Imath::V2f(c_enc.primaries_green_xy[0], c_enc.primaries_green_xy[1]);
    111   chromaticities.blue =
    112       Imath::V2f(c_enc.primaries_blue_xy[0], c_enc.primaries_blue_xy[1]);
    113   chromaticities.white =
    114       Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]);
    115   OpenEXR::addChromaticities(header, chromaticities);
    116   OpenEXR::addWhiteLuminance(header, info.intensity_target);
    117 
    118   auto loadFloat =
    119       format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat;
    120   auto loadAlpha =
    121       has_alpha ? loadFloat : [](const uint8_t* p) -> float { return 1.0f; };
    122 
    123   // Ensure that the destructor of RgbaOutputFile has run before we look at the
    124   // size of `bytes`.
    125   {
    126     InMemoryOStream os(bytes);
    127     OpenEXR::RgbaOutputFile output(
    128         os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
    129     // How many rows to write at once. Again, the OpenEXR documentation
    130     // recommends writing the whole image in one call.
    131     const int y_chunk_size = ysize;
    132     std::vector<OpenEXR::Rgba> output_rows(xsize * y_chunk_size);
    133 
    134     for (size_t start_y = 0; start_y < ysize; start_y += y_chunk_size) {
    135       // Inclusive.
    136       const size_t end_y = std::min(start_y + y_chunk_size - 1, ysize - 1);
    137       output.setFrameBuffer(output_rows.data() - start_y * xsize,
    138                             /*xStride=*/1, /*yStride=*/xsize);
    139       for (size_t y = start_y; y <= end_y; ++y) {
    140         const uint8_t* in_row = &in[(y - start_y) * in_stride];
    141         OpenEXR::Rgba* const JXL_RESTRICT row_data =
    142             &output_rows[(y - start_y) * xsize];
    143         for (size_t x = 0; x < xsize; ++x) {
    144           const uint8_t* in_pixel = &in_row[4 * num_channels * x];
    145           float r = loadFloat(&in_pixel[0]);
    146           float g = loadFloat(&in_pixel[4]);
    147           float b = loadFloat(&in_pixel[8]);
    148           const float alpha = loadAlpha(&in_pixel[12]);
    149           if (!alpha_is_premultiplied) {
    150             r *= alpha;
    151             g *= alpha;
    152             b *= alpha;
    153           }
    154           row_data[x] = OpenEXR::Rgba(r, g, b, alpha);
    155         }
    156       }
    157       output.writePixels(/*numScanLines=*/end_y - start_y + 1);
    158     }
    159   }
    160 
    161   return true;
    162 }
    163 
    164 class EXREncoder : public Encoder {
    165   std::vector<JxlPixelFormat> AcceptedFormats() const override {
    166     std::vector<JxlPixelFormat> formats;
    167     for (const uint32_t num_channels : {1, 2, 3, 4}) {
    168       for (const JxlDataType data_type : {JXL_TYPE_FLOAT}) {
    169         for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
    170           formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
    171                                            /*data_type=*/data_type,
    172                                            /*endianness=*/endianness,
    173                                            /*align=*/0});
    174         }
    175       }
    176     }
    177     return formats;
    178   }
    179   Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
    180                 ThreadPool* pool) const override {
    181     JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
    182     encoded_image->icc.clear();
    183     encoded_image->bitstreams.clear();
    184     encoded_image->bitstreams.reserve(ppf.frames.size());
    185     for (const auto& frame : ppf.frames) {
    186       JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
    187       encoded_image->bitstreams.emplace_back();
    188       JXL_RETURN_IF_ERROR(EncodeImageEXR(frame.color, ppf.info,
    189                                          ppf.color_encoding, pool,
    190                                          &encoded_image->bitstreams.back()));
    191     }
    192     return true;
    193   }
    194 };
    195 
    196 }  // namespace
    197 #endif
    198 
    199 std::unique_ptr<Encoder> GetEXREncoder() {
    200 #if JPEGXL_ENABLE_EXR
    201   return jxl::make_unique<EXREncoder>();
    202 #else
    203   return nullptr;
    204 #endif
    205 }
    206 
    207 }  // namespace extras
    208 }  // namespace jxl