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